// Copyright (c) 2023 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::structure::DomException;
use core::fmt::{Debug, Display, Formatter};
use std::string::FromUtf8Error;

/// Represents an XML related error.
#[derive(Debug)]
pub struct XmlError {
    inner: Box<XmlErrorKind>,
}

impl std::error::Error for XmlError {}

impl Display for XmlError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", *self.inner)
    }
}

/// Different kinds of errors that may occur when working with XML.
#[derive(Debug)]
pub enum XmlErrorKind {
    /// Represents I/O related errors.
    Io(std::io::Error),
    /// Represents errors related to Document Object Model (DOM) operations.
    Dom(DomException),
    /// Represents errors that may occur during parsing of XML.
    Parse(ParseError),
    /// Represents errors that may occur during reading of XML.
    Read(ReadError),
    /// Errors to convert serde-related errors.
    Custom(String),
    /// Represents other miscellaneous errors that may occur.
    Other(OtherError),
}

/// Represents errors that may occur during reading of XML.
#[derive(Debug)]
pub enum ReadError {
    /// Represents invalid UTF-8 character errors.
    InvalidUtf8,
}

/// Represents errors that may occur during parsing of XML.
#[derive(Debug)]
pub enum ParseError {
    /// Represents an error due to unexpected end of XML input.
    UnexpectedEndOfXml(usize), // line
    /// Represents an error due to unexpected character in the XML input.
    UnexpectedCharacter(usize, usize, char), // line, column, character,
    /// Represents an error due to trailing bytes in the XML input.
    TrailingBytes,
    /// Represents an error due to invalid unicode character in the XML input.
    InvalidUnicode,
    /// Represents an error due to overflow during parsing.
    Overflowed,
    /// Represents an error due to unexpected character in the XML DTD part.
    IncorrectDTD(DTDError),
}

#[derive(Debug)]
pub enum DTDError {
    /// Represents errors if DTD content lack of '['.
    MissLeftSquareBracket,
    /// Represents errors if DTD content lack of ']'.
    MissRightSquareBracket,
}

/// Represents other miscellaneous errors that may occur.
#[derive(Debug)]
pub enum OtherError {
    /// Represents errors during transformation.
    Transform,
    /// Represents errors from utf-8.
    FromUtf8(FromUtf8Error),
    /// Represents errors when tag stack is incorrect.
    IncorrectStackLayers,
    /// Represents errors when the num of elements in seq is incorrect.
    IncorrectElementNumber,
}

impl From<std::io::Error> for XmlError {
    fn from(e: std::io::Error) -> Self {
        Self {
            inner: Box::new(XmlErrorKind::Io(e)),
        }
    }
}

impl From<DomException> for XmlError {
    fn from(e: DomException) -> Self {
        Self {
            inner: Box::new(XmlErrorKind::Dom(e)),
        }
    }
}

impl From<ParseError> for XmlError {
    fn from(e: ParseError) -> Self {
        Self {
            inner: Box::new(XmlErrorKind::Parse(e)),
        }
    }
}

impl From<ReadError> for XmlError {
    fn from(e: ReadError) -> Self {
        Self {
            inner: Box::new(XmlErrorKind::Read(e)),
        }
    }
}

impl From<OtherError> for XmlError {
    fn from(e: OtherError) -> Self {
        Self {
            inner: Box::new(XmlErrorKind::Other(e)),
        }
    }
}

impl From<FromUtf8Error> for XmlError {
    fn from(value: FromUtf8Error) -> Self {
        Self {
            inner: Box::new(XmlErrorKind::Other(OtherError::FromUtf8(value))),
        }
    }
}

impl serde::ser::Error for XmlError {
    fn custom<T>(msg: T) -> Self
    where
        T: Display,
    {
        Self {
            inner: Box::new(XmlErrorKind::Custom(msg.to_string())),
        }
    }
}

#[cfg(test)]
mod ut_errors {
    use serde::ser::Error;

    use crate::error::DTDError;
    use crate::{error, DomException, OtherError, ParseError, ReadError, XmlError, XmlErrorKind};

    impl PartialEq for XmlError {
        fn eq(&self, other: &Self) -> bool {
            self.inner == other.inner
        }
    }

    impl PartialEq for XmlErrorKind {
        fn eq(&self, other: &Self) -> bool {
            match (self, other) {
                (Self::Io(_), Self::Io(_)) => true,
                (Self::Dom(a), Self::Dom(b)) => a == b,
                (Self::Parse(a), Self::Parse(b)) => a == b,
                (Self::Read(a), Self::Read(b)) => a == b,
                (Self::Custom(a), Self::Custom(b)) => a == b,
                (Self::Other(a), Self::Other(b)) => a == b,
                _ => false,
            }
        }
    }

    impl PartialEq for ParseError {
        fn eq(&self, other: &Self) -> bool {
            match (self, other) {
                (Self::UnexpectedEndOfXml(a_line), Self::UnexpectedEndOfXml(b_line)) => {
                    a_line == b_line
                }
                (
                    Self::UnexpectedCharacter(a_line, a_column, a_char),
                    Self::UnexpectedCharacter(b_line, b_column, b_char),
                ) => a_line == b_line && a_column == b_column && a_char == b_char,
                (Self::TrailingBytes, Self::TrailingBytes) => true,
                (Self::InvalidUnicode, Self::InvalidUnicode) => true,
                (Self::Overflowed, Self::Overflowed) => true,
                _ => false,
            }
        }
    }

    impl PartialEq for ReadError {
        fn eq(&self, other: &Self) -> bool {
            match (self, other) {
                (Self::InvalidUtf8, Self::InvalidUtf8) => true,
            }
        }
    }

    impl PartialEq for DTDError {
        fn eq(&self, other: &Self) -> bool {
            matches!(
                (self, other),
                (Self::MissLeftSquareBracket, Self::MissLeftSquareBracket)
                    | (Self::MissRightSquareBracket, Self::MissRightSquareBracket)
            )
        }
    }

    impl PartialEq for OtherError {
        fn eq(&self, other: &Self) -> bool {
            match (self, other) {
                (Self::Transform, Self::Transform) => true,
                (Self::IncorrectStackLayers, Self::IncorrectStackLayers) => true,
                (Self::FromUtf8(a), Self::FromUtf8(b)) => a == b,
                (Self::IncorrectStackLayers, Self::IncorrectElementNumber) => true,
                _ => false,
            }
        }
    }

    /// UT test for error debug derive`.
    ///
    /// # Title
    /// ut_xml_err_derive
    ///
    /// # Brief
    /// 1. Calls XmlError content.
    /// 2. Checks if the test results are correct.
    #[test]
    fn ut_xml_err_derive() {
        let res =
            error::XmlError::from(std::io::Error::new(std::io::ErrorKind::Other, "error")).inner;
        let xml = XmlError { inner: res };
        assert_eq!(
            format!("{:?}", &xml),
            "XmlError { inner: Io(Custom { kind: Other, error: \"error\" }) }"
        );

        let res = error::XmlError::from(DomException::IndexSizeErr).inner;
        let xml = XmlError { inner: res };
        assert_eq!(
            format!("{:?}", &xml),
            "XmlError { inner: Dom(IndexSizeErr) }"
        );

        let res = error::XmlError::from(ReadError::InvalidUtf8).inner;
        let xml = XmlError { inner: res };
        assert_eq!(format!("{}", &xml), "Read(InvalidUtf8)");

        let bytes = vec![0, 159];
        if let Err(value) = String::from_utf8(bytes) {
            let res = error::XmlError::from(value).inner;
            let xml = XmlError { inner: res };
            assert_eq!(format!("{}", &xml), "Other(FromUtf8(FromUtf8Error { bytes: [0, 159], error: Utf8Error { valid_up_to: 1, error_len: Some(1) } }))");
        }

        let msg = "abc";
        let res = error::XmlError::custom(msg).inner;
        let xml = XmlError { inner: res };
        assert_eq!(format!("{}", &xml), "Custom(\"abc\")");
    }
}
